package rewardBurn

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"io/ioutil"
	"miners-monitor/models/common"
	"miners-monitor/models/miner"
	"miners-monitor/models/msg"
	"net/http"
	"strconv"
	"time"

	"github.com/filecoin-project/go-state-types/abi"
	"github.com/filecoin-project/lotus/chain/types"
	"github.com/milkbobo/fishgoweb/language"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

var SyncMsgMinerMap map[string]int64
var SyncRewardMinerMap map[string]int64
var SyncBlockMinerMap map[string]int64

type RewardBurnAoModel struct {
	common.BaseModel
	CommonFunc common.CommonFunc
	RewardDb   RewardBurnDbModel
	MessageAo  msg.MessageAoModel
	MinerAo    miner.MinerAoModel
}

func (this *RewardBurnAoModel) FindByStruct(filter RewardBurnBlock, opts *options.FindOptions) []RewardBurnBlock {
	filterBsonByte, err := this.CommonFunc.BsonMarshal(filter)
	if err != nil {
		panic(err)
	}
	mongoResult := this.RewardDb.Find(filterBsonByte, opts)
	result := []RewardBurnBlock{}
	err = mongoResult.All(context.TODO(), &result)
	if err == mongo.ErrNoDocuments {
		return []RewardBurnBlock{}
	} else if err != nil {
		panic(err)
	}
	return result
}

func (this *RewardBurnAoModel) Find(filter bson.D, opts *options.FindOptions) []RewardBurnBlock {
	mongoResult := this.RewardDb.Find(filter, opts)
	result := []RewardBurnBlock{}
	err := mongoResult.All(context.TODO(), &result)
	if err == mongo.ErrNoDocuments {
		return []RewardBurnBlock{}
	} else if err != nil {
		panic(err)
	}
	return result
}

func (this *RewardBurnAoModel) FindOne(filter RewardBurnBlock, opts *options.FindOneOptions) RewardBurnBlock {
	mongoResult := this.RewardDb.FindOne(filter, opts)
	result := RewardBurnBlock{}
	err := mongoResult.Decode(&result)
	if err == mongo.ErrNoDocuments {
		return RewardBurnBlock{}
	} else if err != nil {
		panic(err)
	}
	return result
}

func (this *RewardBurnAoModel) GetSubcalls(data []types.ExecutionTrace, targetTipSet *types.TipSet, isPrint bool) {
	minerIds := this.MinerAo.GetMinerIds()

	for _, subcall := range data {
		msg := subcall.Msg
		for _, singleMinerId := range minerIds {
			if (msg.To.String() == singleMinerId && msg.From.String() == "f02") ||
				(msg.To.String() == "f099" && msg.From.String() == singleMinerId) {

				params := base64.StdEncoding.EncodeToString(msg.Params)
				insertData := RewardBurnBlock{
					CID:        msg.Cid().String(),
					Height:     int64(targetTipSet.Height()),
					To:         msg.To.String(),
					From:       msg.From.String(),
					GasFeeCap:  msg.GasFeeCap.String(),
					GasLimit:   msg.GasLimit,
					GasPremium: msg.GasPremium.String(),
					Method:     int64(msg.Method),
					Nonce:      msg.Nonce,
					Params:     params,
					Value:      msg.Value.String(),
					Version:    int(msg.Version),
					Time:       int64(targetTipSet.Blocks()[0].Timestamp),
				}
				// if insertData.To == "f07830" || insertData.From == "f07830" {
				// 	this.Log.Debug("%+v", insertData)
				// }
				this.RewardDb.Upsert(insertData)
			}
			if len(subcall.Subcalls) > 0 {
				this.GetSubcalls(subcall.Subcalls, targetTipSet, isPrint)
			}
		}
	}
}

func (this *RewardBurnAoModel) AddByHeight(targetTipSet *types.TipSet, isPrint bool) {

	ctx := context.Background()
	o, err := common.LotusApi.StateCompute(ctx, targetTipSet.Height(), []*types.Message{}, targetTipSet.Key())
	if err != nil {
		this.Log.Debug("%+v,%+v", targetTipSet.Height().String(), err)
		return
	}

	minerIds := this.MinerAo.GetMinerIds()
	for _, v := range o.Trace {
		msg2 := v.Msg
		for _, singleMinerId := range minerIds {
			if (msg2.To.String() == singleMinerId && msg2.From.String() == "f02") ||
				(msg2.To.String() == "f099" && msg2.From.String() == singleMinerId) {
				params := base64.StdEncoding.EncodeToString(msg2.Params)
				insertData := RewardBurnBlock{
					CID:        msg2.Cid().String(),
					Height:     int64(targetTipSet.Height()),
					To:         msg2.To.String(),
					From:       msg2.From.String(),
					GasFeeCap:  msg2.GasFeeCap.String(),
					GasLimit:   msg2.GasLimit,
					GasPremium: msg2.GasPremium.String(),
					Method:     int64(msg2.Method),
					Nonce:      msg2.Nonce,
					Params:     params,
					Value:      msg2.Value.String(),
					Version:    int(msg2.Version),
					Time:       int64(targetTipSet.Blocks()[0].Timestamp),
				}
				// if insertData.To == "f07830" || insertData.From == "f07830" {
				// 	this.Log.Debug("%+v", insertData)
				// }
				this.RewardDb.Upsert(insertData)
			}
		}

		msg3 := v.ExecutionTrace.Msg
		for _, singleMinerId := range minerIds {
			if (msg3.To.String() == singleMinerId && msg3.From.String() == "f02") ||
				(msg3.To.String() == "f099" && msg3.From.String() == singleMinerId) {
				params := base64.StdEncoding.EncodeToString(msg3.Params)
				insertData := RewardBurnBlock{
					CID:        msg3.Cid().String(),
					Height:     int64(targetTipSet.Height()),
					To:         msg3.To.String(),
					From:       msg3.From.String(),
					GasFeeCap:  msg3.GasFeeCap.String(),
					GasLimit:   msg3.GasLimit,
					GasPremium: msg3.GasPremium.String(),
					Method:     int64(msg3.Method),
					Nonce:      msg3.Nonce,
					Params:     params,
					Value:      msg3.Value.String(),
					Version:    int(msg3.Version),
					Time:       int64(targetTipSet.Blocks()[0].Timestamp),
				}
				// if insertData.To == "f07830" || insertData.From == "f07830" {
				// 	this.Log.Debug("%+v", insertData)
				// }
				this.RewardDb.Upsert(insertData)
			}
		}
		for _, subcall := range v.ExecutionTrace.Subcalls {
			for _, singleMinerId := range minerIds {
				if (subcall.Msg.To.String() == singleMinerId && subcall.Msg.From.String() == "f02") ||
					(subcall.Msg.To.String() == "f099" && subcall.Msg.From.String() == singleMinerId) {
					msg := subcall.Msg
					params := base64.StdEncoding.EncodeToString(msg.Params)
					insertData := RewardBurnBlock{
						CID:        msg.Cid().String(),
						Height:     int64(targetTipSet.Height()),
						To:         msg.To.String(),
						From:       msg.From.String(),
						GasFeeCap:  msg.GasFeeCap.String(),
						GasLimit:   msg.GasLimit,
						GasPremium: msg.GasPremium.String(),
						Method:     int64(msg.Method),
						Nonce:      msg.Nonce,
						Params:     params,
						Value:      msg.Value.String(),
						Version:    int(msg.Version),
						Time:       int64(targetTipSet.Blocks()[0].Timestamp),
					}
					this.RewardDb.Upsert(insertData)
				}
			}
			for _, subcall := range v.ExecutionTrace.Subcalls {
				this.GetSubcalls(subcall.Subcalls, targetTipSet, isPrint)
			}
		}

		for _, singleMinerId := range minerIds {
			if v.Msg.To.String() == singleMinerId ||
				v.Msg.From.String() == singleMinerId {
				insertData := msg.Message{
					CID:                v.MsgCid.String(),
					Version:            int(v.Msg.Version),
					To:                 v.Msg.To.String(),
					From:               v.Msg.From.String(),
					Error:              v.Error,
					Nonce:              int(v.Msg.Nonce),
					Value:              v.Msg.Value.String(),
					GasLimit:           int(v.Msg.GasLimit),
					GasFeeCap:          v.Msg.GasFeeCap.String(),
					GasPremium:         v.Msg.GasPremium.String(),
					Method:             int(v.Msg.Method),
					GasUsed:            v.GasCost.GasUsed.String(),
					BaseFeeBurn:        v.GasCost.BaseFeeBurn.String(),
					OverEstimationBurn: v.GasCost.OverEstimationBurn.String(),
					MinerPenalty:       v.GasCost.MinerPenalty.String(),
					MinerTip:           v.GasCost.MinerTip.String(),
					Refund:             v.GasCost.Refund.String(),
					TotalCost:          v.GasCost.TotalCost.String(),
					Height:             int64(targetTipSet.Height()),
					Time:               int64(targetTipSet.Blocks()[0].Timestamp),
				}
				this.MessageAo.Upsert(insertData)
			}
		}
	}
}

func (this *RewardBurnAoModel) AddByFilfoxReward(ctx context.Context) {

	go func() {
		defer language.CatchCrash(func(exception language.Exception) {
			this.Log.Critical("AddByFilfox Crash Code:[%d] Message:[%s]\nStackTrace:[%s]", exception.GetCode(), exception.GetMessage(), exception.GetStackTrace())
			if len(SyncRewardMinerMap) > 0 {
				time.Sleep(time.Minute)
				this.AddByFilfoxReward(ctx)
			}
		})

		this.Log.Debug(":%v", "AddByFilfoxReward")

		SyncRewardMinerMap = make(map[string]int64)
		// SyncRewardMinerMap["f07830"] = 0
		minerIds := this.MinerAo.GetMinerIds()
		for _, singleMinerId := range minerIds {
			SyncRewardMinerMap[singleMinerId] = 0
		}
		pageIndex := 0
		for {
			for singleMinerId, _ := range SyncRewardMinerMap {
				resp, err := http.Get("https://filfox.info/api/v1/address/" + singleMinerId + "/transfers?pageSize=100&page=" + strconv.Itoa(pageIndex))
				if err != nil {
					panic(err)
				}
				bytes, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					panic(err)
				}
				result := FilFoxTransfers{}
				err = json.Unmarshal(bytes, &result)
				if err != nil {
					language.Throw(1, err.Error())
				}

				for _, transfer := range result.Transfers {
					if transfer.Type == "reward" || transfer.Type == "burn" {
						// if transfer.Type == "reward" {

						getReward := this.FindOne(RewardBurnBlock{To: transfer.To, From: transfer.From, Height: transfer.Height}, options.FindOne())
						if getReward.Value == "" {
							targetTipSet, err := common.LotusApi.ChainGetTipSetByHeight(context.TODO(), abi.ChainEpoch(transfer.Height), common.NowTipSet.Key())
							if err != nil {
								panic(err)
							}
							this.AddByHeight(targetTipSet, false)
						}
						SyncRewardMinerMap[singleMinerId] = transfer.Height
					}
				}

				if len(result.Transfers) <= 0 {
					delete(SyncRewardMinerMap, singleMinerId)
					continue
				}

				if len(SyncRewardMinerMap) == 0 {
					break
				}
				time.Sleep(200 * time.Millisecond)
			}
			if len(SyncRewardMinerMap) == 0 {
				break
			}
			pageIndex++
			select {
			case <-ctx.Done():
				return
			default:
			}
		}
	}()
}

func (this *RewardBurnAoModel) AddByFilfoxMessages(ctx context.Context) {

	go func() {
		defer language.CatchCrash(func(exception language.Exception) {
			this.Log.Critical("AddByFilfoxMessages Crash Code:[%d] Message:[%s]\nStackTrace:[%s]", exception.GetCode(), exception.GetMessage(), exception.GetStackTrace())
			if len(SyncMsgMinerMap) > 0 {
				time.Sleep(time.Minute)
				this.AddByFilfoxMessages(ctx)
			}
		})

		this.Log.Debug(":%v", "AddByFilfoxMessages")
		SyncMsgMinerMap = make(map[string]int64)
		minerIds := this.MinerAo.GetMinerIds()
		for _, singleMinerId := range minerIds {
			SyncMsgMinerMap[singleMinerId] = 0
		}
		pageIndex := 0
		for {
			//singleMinerId := "f07749"
			for singleMinerId, _ := range SyncMsgMinerMap {
				resp, err := http.Get("https://filfox.info/api/v1/address/" + singleMinerId + "/messages?pageSize=100&page=" + strconv.Itoa(pageIndex))
				if err != nil {
					panic(err)
				}
				bytes, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					panic(err)
				}
				result := FilFoxMessages{}
				err = json.Unmarshal(bytes, &result)
				if err != nil {
					language.Throw(1, err.Error())
				}

				for _, transfer := range result.Messages {
					getReward := this.MessageAo.FindOne(msg.Message{CID: transfer.Cid}, options.FindOne())
					if getReward.Value == "" {
						//this.Log.Debug("%+v",transfer)
						targetTipSet, err := common.LotusApi.ChainGetTipSetByHeight(context.TODO(), abi.ChainEpoch(transfer.Height), common.NowTipSet.Key())
						if err != nil {
							panic(err)
						}
						this.AddByHeight(targetTipSet, false)
					}
					SyncMsgMinerMap[singleMinerId] = transfer.Height
				}

				if len(result.Messages) <= 0 {
					delete(SyncMsgMinerMap, singleMinerId)
					continue
				}

				if len(SyncMsgMinerMap) == 0 {
					break
				}
				time.Sleep(200 * time.Millisecond)
			}
			if len(SyncMsgMinerMap) == 0 {
				break
			}
			pageIndex++
			select {
			case <-ctx.Done():
				return
			default:
			}
		}
	}()
}

func (this *RewardBurnAoModel) SyncYesterdayByFilfox() {
	this.Log.Debug(":%v", "SyncYesterdayByFilfox")

	rewardMinerMap := map[string]bool{}
	minerIds := this.MinerAo.GetMinerIds()
	for _, singleMinerId := range minerIds {
		rewardMinerMap[singleMinerId] = true
	}
	pageIndex := 0
	yesterdayTime := time.Now().In(common.LocationTime).AddDate(0, 0, -1)
	yesterdayZeroHore := time.Date(yesterdayTime.Year(), yesterdayTime.Month(), yesterdayTime.Day(), 0, 0, 0, 0, common.LocationTime)
	for {
		//singleMinerId := "f07749"
		for singleMinerId, _ := range rewardMinerMap {
			resp, err := http.Get("https://filfox.info/api/v1/address/" + singleMinerId + "/transfers?pageSize=100&page=" + strconv.Itoa(pageIndex))
			if err != nil {
				panic(err)
			}
			bytes, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				panic(err)
			}
			result := FilFoxTransfers{}
			err = json.Unmarshal(bytes, &result)
			if err != nil {
				panic(err)
			}

			for _, transfer := range result.Transfers {
				if transfer.Type == "reward" || transfer.Type == "burn" {
					if transfer.Timestamp < yesterdayZeroHore.Unix() {
						if rewardMinerMap[singleMinerId] {
							delete(rewardMinerMap, singleMinerId)
						}
						continue
					}
					//this.Log.Debug("singleMinerId:%+v", transfer)
					getReward := this.FindOne(RewardBurnBlock{To: transfer.To, From: transfer.From, Height: transfer.Height}, options.FindOne())
					if getReward.Value == "" {
						targetTipSet, err := common.LotusApi.ChainGetTipSetByHeight(context.TODO(), abi.ChainEpoch(transfer.Height), common.NowTipSet.Key())
						if err != nil {
							panic(err)
						}
						this.AddByHeight(targetTipSet, false)
						if transfer.Timestamp < yesterdayZeroHore.Unix() {
							if rewardMinerMap[singleMinerId] {
								delete(rewardMinerMap, singleMinerId)
							}
						}
					}
				}
			}

			if len(result.Transfers) <= 0 {
				delete(rewardMinerMap, singleMinerId)
				continue
			}

			if len(rewardMinerMap) == 0 {
				break
			}
			time.Sleep(200 * time.Millisecond)
		}
		if len(rewardMinerMap) == 0 {
			this.Log.Debug("%+v", "end")
			break
		}
		pageIndex++
	}
}

func (this *RewardBurnAoModel) SyncYesterdayMessagesByFilfox() {
	this.Log.Debug(":%v", "SyncYesterdayByFilfox")

	rewardMinerMap := map[string]bool{}
	minerIds := this.MinerAo.GetMinerIds()
	for _, singleMinerId := range minerIds {
		rewardMinerMap[singleMinerId] = true
	}
	pageIndex := 0
	yesterdayTime := time.Now().In(common.LocationTime).AddDate(0, 0, -1)
	yesterdayZeroHore := time.Date(yesterdayTime.Year(), yesterdayTime.Month(), yesterdayTime.Day(), 0, 0, 0, 0, common.LocationTime)
	for {
		//singleMinerId := "f07749"
		for singleMinerId, _ := range rewardMinerMap {
			resp, err := http.Get("https://filfox.info/api/v1/address/" + singleMinerId + "/messages?pageSize=100&page=" + strconv.Itoa(pageIndex))
			if err != nil {
				panic(err)
			}
			bytes, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				panic(err)
			}
			result := FilFoxMessages{}
			err = json.Unmarshal(bytes, &result)
			if err != nil {
				panic(err)
			}

			for _, transfer := range result.Messages {
				if transfer.Timestamp < yesterdayZeroHore.Unix() {
					if rewardMinerMap[singleMinerId] {
						delete(rewardMinerMap, singleMinerId)
					}
					continue
				}
				//this.Log.Debug("singleMinerId:%+v", transfer)
				getReward := this.MessageAo.FindOne(msg.Message{CID: transfer.Cid}, options.FindOne())
				if getReward.Value == "" {
					targetTipSet, err := common.LotusApi.ChainGetTipSetByHeight(context.TODO(), abi.ChainEpoch(transfer.Height), common.NowTipSet.Key())
					if err != nil {
						panic(err)
					}
					this.AddByHeight(targetTipSet, false)
					if transfer.Timestamp < yesterdayZeroHore.Unix() {
						if rewardMinerMap[singleMinerId] {
							delete(rewardMinerMap, singleMinerId)
						}
					}
				}
			}

			if len(result.Messages) <= 0 {
				delete(rewardMinerMap, singleMinerId)
				continue
			}

			if len(rewardMinerMap) == 0 {
				break
			}
			time.Sleep(200 * time.Millisecond)
		}
		if len(rewardMinerMap) == 0 {
			this.Log.Debug("%+v", "end")
			break
		}
		pageIndex++
	}
}

func (this *RewardBurnAoModel) SyncByFilfoxBlock(ctx context.Context) {

	go func() {
		defer language.CatchCrash(func(exception language.Exception) {
			this.Log.Critical("SyncByFilfoxBlock Crash Code:[%d] Message:[%s]\nStackTrace:[%s]", exception.GetCode(), exception.GetMessage(), exception.GetStackTrace())
			if len(SyncBlockMinerMap) > 0 {
				time.Sleep(time.Minute)
				this.SyncByFilfoxBlock(ctx)
			}
		})

		this.Log.Debug("%v", "SyncFilfoxByMinerId")

		SyncBlockMinerMap = make(map[string]int64)
		//SyncBlockMinerMap["f07830"] = 0
		minerIds := this.MinerAo.GetMinerIds()
		for _, singleMinerId := range minerIds {
			SyncBlockMinerMap[singleMinerId] = 0
		}
		for minerId, _ := range SyncBlockMinerMap {
			pageIndex := 0
			for {
				//this.Log.Debug("pageIndex:%v", pageIndex)
				resp, err := http.Get("https://filfox.info/api/v1/address/" + minerId + "/blocks?pageSize=100&page=" + strconv.Itoa(pageIndex))
				if err != nil {
					panic(err)
				}
				bytes, err := ioutil.ReadAll(resp.Body)

				if err != nil {
					panic(err)
				}
				result := FilFoxBlocks{}
				err = json.Unmarshal(bytes, &result)
				if err != nil {
					panic(err)
				}

				for _, transfer := range result.Blocks {
					getReward := this.FindOne(RewardBurnBlock{To: minerId, From: "f02", Height: transfer.Height, Value: transfer.Reward}, options.FindOne())
					if getReward.Value == "" {
						//this.Log.Debug("%+v",getReward)
						//this.Log.Debug("transfer:%+v", transfer)
						targetTipSet, err := common.LotusApi.ChainGetTipSetByHeight(context.TODO(), abi.ChainEpoch(transfer.Height), common.NowTipSet.Key())
						if err != nil {
							panic(err)
						}
						this.AddByHeight(targetTipSet, true)
					}
					SyncBlockMinerMap[minerId] = transfer.Height
				}

				if len(result.Blocks) == 0 {
					delete(SyncBlockMinerMap, minerId)
					break
				}
				pageIndex++

				select {
				case <-ctx.Done():
					return
				default:
				}

			}
		}
	}()

}
